/*
** Utility classes
*/

function Keyboard() {
    var keys = new Array();
    // Initialize the keys array
    // I don't know what the highest keycode really is
    // but I'm assuming 200 is high enough.
    var i;
    for (i = 0; i < 200; i++) {
        keys[i] = false;
    }
 
    this.keys = keys;

    $(document).keydown(function(event) {
        keys[event.which] = true;
        //event.preventDefault();
    });

    $(document).keyup(function(event) {
        keys[event.which] = false;
        //event.preventDefault();
    });

    this.keyCodes = {
        'enter': 13,
        'space': 32,
        'left': 37,
        'up': 38,
        'right': 39,
        'down': 40,
        '0': 48,
        '1': 49,
        '2': 50,
        '3': 51,
        '4': 52,
        '5': 53,
        '6': 54,
        '7': 55,
        '8': 56,
        '9': 57,
        'a': 65,
        'b': 66,
        'c': 67,
        'd': 68,
        'e': 69,
        'f': 70,
        'g': 71,
        'h': 72,
        'i': 73,
        'j': 74,
        'k': 75,
        'l': 76,
        'm': 77,
        'n': 78,
        'o': 79,
        'p': 80,
        'q': 81,
        'r': 82,
        's': 83,
        't': 84,
        'u': 85,
        'v': 86,
        'w': 87,
        'x': 88,
        'y': 89,
        'z': 90
    };
}

function Vector2(x, y) {
    this.x = x;
    this.y = y;
}

Vector2.prototype.negate = function() {
    return new Vector2(-this.x, -this.y);
};

Vector2.prototype.add = function(vector2) {
    return new Vector2(this.x + vector2.x, this.y + vector2.y);
};

Vector2.prototype.subtract = function(vector2) {
    return this.add(vector2.negate());
};

Vector2.prototype.lengthSquared = function() {
    return this.x * this.x + this.y * this.y;
};

Vector2.prototype.length = function() {
    return Math.sqrt(this.lengthSquared());
}

Vector2.prototype.normalize = function() {
    var l = this.length();
    if (l != 0) {
        return new Vector2(this.x / l, this.y / l);
    }
    return new Vector2(0, 0);
};

Vector2.prototype.scale = function(value) {
    return new Vector2(this.x * value, this.y * value);
};

Vector2.prototype.setLength = function(newLength) {
    var norm = this.normalize();
    return norm.scale(newLength);
}

function Camera() {    
    // Rotation is a little broken right now
    this.position = new Vector2(0, 0);
    
    this.container = new createjs.Container();
    this.visual = this.container;
    
    this.target = new Vector2(0, 0);
    this.trackingConstant = 1;
}

Camera.prototype.update = function(viewportSize) {        
    // Camera velocity is proportional to distance to target, giving a
    // smooth, gliding feel when trackingConstant is less than 1.
    this.position = this.position.add(this.target.subtract(this.position)
                                      .scale(this.trackingConstant));
    var coords = this.position.negate().add(viewportSize.scale(.5)); 
    
    this.container.setTransform(coords.x, coords.y);
}

/*
** Basic setup and game management
*/

$(document).ready(function() {
    window.keyboard = new Keyboard();
    window.game = new Game(document);
});

function Game(document) {    
    // Create a canvas as large as the viewport and add it to the page
    var canvas = document.createElement("canvas");
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    document.body.appendChild(canvas);
    
    this._stage = new createjs.Stage(canvas);
    this._state = null;
    
    this.canvasSize = new Vector2(canvas.width, canvas.height);
    
    // Set up preloading, then wait for completion before starting the game
    this.queue = new createjs.LoadQueue();
    this.queue.installPlugin(createjs.Sound);
    this.queue.addEventListener('complete', $.proxy(this.prepareGame, this));
    var manifest = [
        { id: 'background', src: '/assets/images/background.png' },
        { id: 'foreground', src: '/assets/images/foreground.png' },
        { id: 'dot', src: '/assets/images/dot.png' },
        { id: 'man2', src: '/assets/images/man2.gif' },
        { id: 'walkzones', src: '/assets/images/walkzones.png' },
        { id: 'bgSound', src: '/assets/sounds/FreedomHighwaySample.ogg' }
    ];
    this.queue.loadManifest(manifest);
}

Game.prototype.prepareGame = function() {
    this.transitionTo(new GameState(this));    
    createjs.Ticker.setFPS(30);
    createjs.Ticker.addEventListener("tick", $.proxy(this.gameLoop, this));
    console.log("loaded");
};

Game.prototype.gameLoop = function() {
    this.update();
    this.draw();
};

Game.prototype.update = function() {
    this._state.update();
};

Game.prototype.draw = function() {
    this._stage.update();
};

Game.prototype.transitionTo = function(newState) {
    this._stage.removeAllChildren();
    this._state = newState;
    this._stage.addChild(this._state.visual);
};

/*
 * Characters
 */

CharacterActions = Object.freeze({
    left: {},
    upLeft: {},
    up: {},
    upRight: {},
    right: {},
    downRight: {},
    down: {},
    downLeft: {}
});

/*
 * A character has a visual display and uses its agent to decide on actions
 */

/*function CharacterDisplay(visual, character, queue) {
    // Reset the visual representation to the origin before adding it to the
    // container. This assumes that regX and regY are correctly set earlier.
    visual.x = 0;
    visual.y = 0;
    
    this.visual = new createjs.Container();
    this.visual.addChild(visual);
    
    this.character = character;
    this._speechBubble = new createjs.Bitmap(queue.getResult('speechBubble'));
}

CharacterDisplay.prototype.update = function() {
    this.visual.x = this.character.position.x;
    this.visual.y = this.character.position.y;
}*/

function Character(visual, position, agent) {    
    this.visual = visual;
    this.world = null;
    this.position = position;
    this.agent = agent;
}

Character.prototype.update = function() {
    var newPosition = this.agent(this.world, this);
    if (this.world.isPositionValid(newPosition)) {
        this.position = newPosition;
        this.visual.x = this.position.x;
        this.visual.y = this.position.y;
    }
};

function PlayerAgent(world, character) {
    var delta = new Vector2(0, 0);    
    
    if (keyboard.keys[keyboard.keyCodes['left']]) {
        delta.x -= 1;
    } else if (keyboard.keys[keyboard.keyCodes['right']]) {
        delta.x += 1;
    }
    
    if (keyboard.keys[keyboard.keyCodes['up']]) {
        delta.y -= 1;
    } else if (keyboard.keys[keyboard.keyCodes['down']]) {
        delta.y += 1;
    }

    return character.position.add(delta.setLength(10));
}

function ChaseAgent(world, character) {
    var charPosition = new Vector2(character.position.x, character.position.y);
    var playerPosition = new Vector2(world.player.position.x,
                                     world.player.position.y);
    var k = .05;
    var offset = playerPosition.subtract(charPosition);
    var length = offset.length();
    if (length != 0) {
        offset = offset.setLength(length - 200).scale(k);
        
        var closestOffset = undefined, closestLength = undefined;
        for (var i = 0; i < world.characters.length; i++) {
            var charOffset = world.characters[i].position.subtract(charPosition);
            var charLength = charOffset.length();
            
            if (charLength < 100) {
                charOffset = charOffset.setLength(charLength - 100).scale(k);
                offset = offset.add(charOffset);
            }
        }
        
        var newPosition = charPosition.add(offset);
        return newPosition;
    }
    return charPosition;
}

function StaticAgent(world, character) {
    return character.position;
}

function BinaryMap(image) {
    console.log("started binaryMap creation...");
    
    var canvas = document.createElement("canvas");
    canvas.width = image.width;
    canvas.height = image.height;
    
    var context = canvas.getContext('2d');
    context.drawImage(image, 0, 0);
    
    this._data = new Array();
    this.width = canvas.width;
    this.height = canvas.height;
    
    var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
    for (var i = 0; i < imageData.data.length; i += 4) {
        if (imageData.data[i + 3] != 0) {
            this._data.push(true);
        } else {
            this._data.push(false);
        }
    }
    
    console.log("finished binaryMap creation...");
}

BinaryMap.prototype.valueAt = function(x, y) {
    return this._data[y * this.width + x];
};

function World(walkzone) {
    this.player = null;
    this.characters = new Array();
    this.elapsedTicks = 0;
    this._walkzone = walkzone;
}

World.prototype.addPlayer = function(player) {
    this.player = player;
    player.world = this;
};

World.prototype.addCharacter = function(character) {
    this.characters.push(character);
    character.world = this;
}

World.prototype.update = function() {
    this.player.update();
    for (var i = 0; i < this.characters.length; i++) {
        this.characters[i].update();
    }
    this.elapsedTicks++;
};

World.prototype.isPositionValid = function(position) {
    return !this._walkzone.valueAt(position.x, position.y);
};

/*
** Game states
** A game state is "one unit" of gameplay, like a single level or menu screen.
** A state must have a constructor that takes an instance of the parent Game, 
** an 'update' method, and a 'visual' property that exposes an EaselJS
** DisplayObject that will be rendered to the Game's Stage. 
*/

function GameState(game) {
    this._game = game;
    
    this._backgroundSound = createjs.Sound.createInstance('bgSound');
    this._backgroundSound.play(createjs.Sound.INTERRUPT_ANY, 0, 0, -1);
    console.log(this._backgroundSound);
    
    this._camera = new Camera();
    this._background = new createjs.Bitmap(game.queue.getResult('background'));
    this._foreground = new createjs.Bitmap(game.queue.getResult('foreground'));
    this._background.name = "bg";
    this._foreground.name = "fg";
    
    var playerVisual = new createjs.Bitmap(game.queue.getResult('man2'));
    playerVisual.x = 500;
    playerVisual.y = 500;
    playerVisual.regY = 161;
    playerVisual.regX = 60;
    
    var char2 = new createjs.Bitmap(game.queue.getResult('dot'));
    char2.x = 500;
    char2.y = 500;
    char2.regY = 161;
    char2.regX = 60;
    
    var char3 = new createjs.Bitmap(game.queue.getResult('dot'));
    char3.x = 500;
    char3.y = 500;
    char3.regY = 161;
    char3.regX = 60;
    
    var char4 = new createjs.Bitmap(game.queue.getResult('dot'));
    char4.x = 500;
    char4.y = 500;
    char4.regY = 161;
    char4.regX = 60;
    
    var char5 = new createjs.Bitmap(game.queue.getResult('dot'));
    char5.x = 500;
    char5.y = 500;
    char5.regY = 161;
    char5.regX = 60;
    
    var char6 = new createjs.Bitmap(game.queue.getResult('dot'));
    char6.x = 500;
    char6.y = 500;
    char6.regY = 161;
    char6.regX = 60;
    
    var walkzone = new BinaryMap(game.queue.getResult('walkzones'));
    this._world = new World(walkzone);
    this._world.addPlayer(new Character(
        playerVisual,
        new Vector2(500, 500),
        PlayerAgent));
    this._world.addCharacter(new Character(
        char2,
        new Vector2(500, 600),
        ChaseAgent
    ));
    this._world.addCharacter(new Character(
        char3,
        new Vector2(500, 400),
        ChaseAgent
    ));
    this._world.addCharacter(new Character(
        char4,
        new Vector2(400, 500),
        ChaseAgent
    ));
    this._world.addCharacter(new Character(
        char5,
        new Vector2(600, 500),
        ChaseAgent
    ));
    this._world.addCharacter(new Character(
        char6,
        new Vector2(400, 400),
        StaticAgent
    ));
    this._foreground.x = 180;
    this._foreground.y = 184;
    this.visual = this._camera.visual;
    this._camera.container.addChild(this._background, this._world.player.visual,
        this._world.characters[0].visual, this._world.characters[1].visual,
        this._world.characters[2].visual, this._world.characters[3].visual,
        this._world.characters[4].visual, this._foreground);
    this._camera.position = this._world.player.position;
    this._camera.trackingConstant = .1;

    this._wasBehindBuilding = false;
}

GameState.prototype.update = function() {
    // Adding cheater values for now
    if (this._foreground.hitTest(this._world.player.position.x - 180, this._world.player.position.y - 184)) {
        if (!this._wasBehindBuilding) {
            createjs.Tween.get(this._foreground, { override: true }).to({ alpha: .2 }, 200);
        }
        this._wasBehindBuilding = true;
    } else {
        if (this._wasBehindBuilding) {
            createjs.Tween.get(this._foreground, { override: true }).wait(500).to({ alpha: 1 }, 500);
        }
        this._wasBehindBuilding = false;
    }
    
    this._camera.container.sortChildren(function(a, b) {
        if (a.name == "fg") { return 1; }
        if (a.name == "bg") { return -1; }
        if (b.name == "fg") { return -1; }
        if (b.name == "bg") { return 1; }
        return (a.y > b.y) ? 1 : -1;
    });
    
    this._world.update();
    
    this._camera.target = this._world.player.position;
    this._camera.update(game.canvasSize);
};